iT邦幫忙

2026 iThome 鐵人賽

DAY 28
2
Software Development

從登入到授權-現代軟體的身分架構指南系列 第 28 篇

Day 28|被偷走的 Access Token:如何限制 Token 被冒用?

  • 分享至 

  • xImage
  •  

前言

上一篇從 OAuth 2.0/OIDC 的授權流程出發,介紹 state、PKCE 與 nonce 如何在不同階段降低回應被注入、授權碼被攔截,以及登入結果被重放的風險。

但假設這些檢查都做對了,使用者也完成 MFA,Client 順利拿到 Access Token,故事就結束了嗎?

還沒有。接下來每一次呼叫 API,Client 都需要把 Access Token 交給 Resource Server。如果這顆 Token 在瀏覽器、伺服器 Log 或遭入侵的執行環境中被偷走,攻擊者可能不需要知道密碼,也不需要重新通過 MFA,就能直接拿它存取資源。

因此,今天要回答的問題是:如果攻擊者只偷到 Access Token,卻沒有對應的 Client 金鑰,系統能不能阻止他直接使用這顆 Token?

本篇會先回到 Bearer Token 的安全模型,再介紹短效期限、Scope 與 Audience 能限制的範圍,最後拆解 mTLS 與 DPoP 這兩種 Sender-Constrained Token 機制。

今天內容涵蓋:

  1. Bearer Token 為什麼被偷走後可能被冒用
  2. Access Token 通常從哪裡外洩
  3. 短效期限、Scope 與 Audience 各自限制什麼
  4. Sender-Constrained Token:將 Token 綁定到特定 Client
  5. mTLS:用 Client 憑證綁定 Access Token
  6. DPoP:用每次請求的簽章證明綁定 Token
  7. mTLS 與 DPoP 的差異
  8. 實際部署時要注意的限制

一、Bearer Token 為什麼被偷走後可能被冒用

前面介紹 Token-Based Auth 時,曾把 Bearer Token 比喻成一張「持有即可使用」的通行證。這不是單純的比喻,而是 RFC 6750 §1.2 對 Bearer Token 的核心定義:使用者只要持有 Token,就可以在它允許的範圍內使用,不需要另外證明自己持有某把密碼學金鑰。

一般 API 請求會長這樣:

GET /payroll HTTP/1.1
Host: api.example.com
Authorization: Bearer ACCESS_TOKEN

Resource Server 收到請求後,會驗證 Token 是否可信、是否過期、是否適用於這支 API,以及是否具備需要的權限。但在純 Bearer 模型中,它只確認 Token 本身能不能用,不會確認現在拿著 Token 發出請求的人,是否就是當初取得這顆 Token 的 Client。

因此,如果攻擊者取得一顆尚未失效的 Access Token,就可能把它放進攻擊者自己發出的 API 請求中。只要 Resource Server 判定這顆 Token 仍然有效,且權限與使用範圍符合要求,請求就可能被接受。

JWT 有簽章,為什麼還會被冒用?

這裡最容易混淆的是 Token 的格式與 Token 的使用方式:

  • JWT 是 Token 的格式,重點在「內容能不能被信任」。例如 Token 裡寫了使用者是誰、有哪些權限、什麼時候過期;簽章可以確認這些內容是 Authorization Server 發出的,而且中途沒有被修改。
  • Bearer 是 Token 的使用方式,重點在「誰拿到就能用」。Resource Server 只要看到一顆有效的 Bearer Token,就會依照 Token 內容判斷是否放行,不會再確認拿 Token 的人是不是原本的 Client。

因此,問題不在於 JWT 能不能驗證簽章,而在於 Bearer Token 的使用方式。攻擊者只要拿到一顆尚未失效的 Access Token,就可以原封不動放進自己的 API 請求中;因為 Token 內容沒有被修改,簽章仍然會通過。


二、Access Token 通常從哪裡外洩

Token 竊取不一定發生在網路傳輸途中,也不一定需要破解 HTTPS。Token 抵達 Client 或 Resource Server 之後,仍然可能因為程式漏洞、紀錄方式或執行環境遭入侵而外洩。

實務上,Access Token 可能出現在不同類型的應用與基礎設施中,例如雲端服務、SaaS 平台、容器環境、CI/CD 流程或內部系統的 API 整合。只要這些環境的紀錄、設定檔、記憶體或執行流程被攻擊者取得,Token 就可能被複製並拿去使用。

可以把常見外洩位置整理成三類:

外洩位置 可能發生的情況 風險重點
瀏覽器/Client XSS 讀取 JavaScript 可存取的 Token、惡意擴充套件或裝置遭入侵 Token 已經抵達 Client,風險來自端點或前端程式
Log/除錯與監控系統 Authorization Header、Token Response 被完整寫入 Log、Trace 或錯誤報告 風險來自系統把 Token 記錄下來,或讓它進入可被查詢的監控資料
伺服器/容器/CI/CD 程式執行環境遭入侵,Token 從檔案、記憶體或工作流程中被取走 風險來自執行環境或憑證管理,而不是單純的網路傳輸

例如,一個報表系統的情境可能是:

使用者完成登入與 MFA
        ↓
Client 取得有效的 Bearer Access Token
        ↓
除錯程式把完整 Authorization Header 寫入 Log
        ↓
攻擊者取得 Log 中的 Token
        ↓
從其他環境帶著同一顆 Token 呼叫 API
        ↓
若 Token 與存取條件仍有效,API 可能放行

這裡並不是 MFA 被破解,而是攻擊者拿到了 MFA 完成之後核發的存取憑證。如果 API 只要求有效 Token,攻擊者自然不必重新走一次登入流程。

同樣地,PKCE 主要保護 Authorization Code 的交換;它不會自動讓交換完成後的 Access Token 具有防冒用能力。

⚠️這裡討論的是「合法 Client 取得的 Access Token 被複製走」的情境。如果使用者一開始就把權限授予惡意 App,問題就不只是 Token 被偷,而是授權對象本身已經不可信,這時要先避免使用者把權限授予不可信的 App,例如限制哪些 App 可以被授權、檢查 App 要求的權限是否合理,並在授權畫面清楚提醒使用者。


三、短效期限、Scope 與 Audience 各自限制什麼

在導入金鑰綁定之前,先把 Token 本身的使用範圍收斂,仍然是最基本的防護。

可以從三個問題來看:可以用多久、可以做什麼,以及可以用在哪裡。

限制 對應的控制重點 能降低的風險 無法單獨解決的事
有效期限 這顆 Token 可以用多久? 縮短外洩後可以被利用的時間 過期之前仍可能被冒用
Scope 這顆 Token 可以要求哪些操作? 限制攻擊者取得的權限範圍 被允許的操作仍可能由攻擊者執行
Audience 這顆 Token 是發給哪個 Resource Server 的? 避免 Token 被拿到非預期的 API 使用 攻擊者仍可能拿它呼叫原本的 API

這些限制都沒有改變 Bearer 的本質:在允許的時間、操作與 API 範圍內,偷到 Token 的人仍可能使用它。

這些限制需要由 Resource Server 實際檢查,才會產生效果;不能只把有效期限、Scope 或 Audience 寫進 Token,就視為已完成防護。有效期限也應依資源敏感度、撤銷能力與使用情境調整,而不是套用固定數字。

Refresh Token 外洩會讓風險延續

縮短 Access Token 效期,只能減少它被偷走後可以使用的時間;但如果攻擊者連 Refresh Token 也取得,就可能繼續換到新的 Access Token。

因此,Refresh Token 也需要保護。常見做法包括:

  • Refresh Token Rotation: 每次刷新時都核發新的 Refresh Token,並讓舊的失效。
  • Sender-Constraining: 把 Refresh Token 綁定到特定 Client 或金鑰,避免只有 Token 就能換新憑證。

如果系統發現已失效的 Refresh Token 又被拿來使用,就可以判斷可能發生外洩,並撤銷相關 Token。

需要注意的是,這些機制主要是在保護「後續繼續換新 Token」的能力;已經發出去的 Access Token,仍需要靠有效期限、撤銷機制或 Resource Server 的檢查來限制風險。


四、Sender-Constrained Token:將 Token 綁定到特定 Client

前三節先說明了 Bearer Token 的風險:只要 Access Token 仍然有效,攻擊者取得後就可能直接拿去呼叫 API。短效期限、Scope 與 Audience 可以縮小影響範圍,但仍無法改變「持有 Token 就可能使用」這件事。

Sender-Constrained Token 要處理的,就是這個問題。它的核心概念是:Resource Server 不只檢查 Access Token 是否有效,也要確認送出請求的 Client 是否持有與 Token 綁定的金鑰。

也就是說,攻擊者即使偷到 Access Token,如果沒有對應的私鑰或憑證,仍然無法完成驗證。

可以把流程整理成三個步驟:

  1. Client 準備金鑰: Client 先建立或取得一組金鑰,並保護好私鑰。
  2. Authorization Server 綁定 Token: 核發 Access Token 時,Authorization Server 會把這顆 Token 和 Client 的憑證或公開金鑰建立關聯。
  3. Resource Server 驗證持有證明: Client 呼叫 API 時,除了送出 Access Token,還要證明自己持有對應的私鑰。Resource Server 會同時檢查 Token 與這份持有證明。

這類「證明自己持有某把私鑰」的設計,稱為 Proof of Possession(PoP,持有證明)。重點不是把私鑰交給伺服器,而是透過簽章或 TLS 握手等方式,讓伺服器確認 Client 真的能使用那把私鑰。

本篇接著介紹兩種常見做法:

  • mTLS/Certificate-Bound Access Token(RFC 8705): 透過 Client 憑證與 TLS 握手,證明持有對應的私鑰。
  • DPoP(RFC 9449): 在每次 HTTP 請求中加入由 Client 私鑰簽署的 Proof JWT。

💡這裡真正被綁定的是金鑰或憑證,不是 client_id、IP 位址或 User-Agent。

client_id 通常是公開識別碼,不能單獨證明呼叫者就是原本的 Client;IP 位址與 User-Agent 可以作為輔助風險訊號,但不能取代密碼學上的持有證明。


五、mTLS:用 Client 憑證綁定 Access Token

mTLS(Mutual TLS,雙向 TLS)的重點是:把 Access Token 綁定到某一張 Client 憑證。之後呼叫 API 時,Resource Server 會同時檢查 Token 是否有效,以及本次連線使用的憑證是否與 Token 綁定資訊相符。

如果攻擊者只偷到 Access Token,卻沒有那張憑證對應的私鑰,就無法建立正確的 mTLS 連線,也就不能直接冒用這顆 Token。

https://ithelp.ithome.com.tw/upload/images/20260916/20181928GCjonKhkds.png

上圖可以分成兩個階段來看。

第一個階段,是 Client 向 Authorization Server 取得 Token:

  1. Client 與 Authorization Server 建立 mTLS 連線,出示 Client 憑證,並證明自己持有對應私鑰。
  2. Client 送出 Token Request。
  3. Authorization Server 驗證授權條件後,核發 Access Token,並把這顆 Token 綁定到該 Client 憑證。
  4. Authorization Server 回傳 Certificate-Bound Access Token。

第二個階段,是 Client 使用 Token 呼叫 Resource Server:

  1. Client 使用同一張憑證與 Resource Server 建立 mTLS 連線。
  2. Client 帶著 Access Token 呼叫 API。
  3. Resource Server 驗證 Token,也比對本次 mTLS 連線中的 Client 憑證是否符合 Token 的綁定資訊。
  4. 只有 Token 有效、憑證也相符時,API 才會放行。

這就是 mTLS 防止 Token 冒用的核心:偷到 Token 還不夠,還必須拿得出 Token 綁定的那張憑證私鑰。

mTLS 的兩種用途不要混在一起

mTLS 在 OAuth 2.0 裡常見有兩種用途:

機制 發生位置 目的
mTLS Client Authentication Client 向 Authorization Server 換 Token 時 證明是哪個 Client 來換 Token
Certificate-Bound Access Token Token 核發後,Client 呼叫 Resource Server 時 限制這顆 Access Token 只能搭配特定 Client 憑證使用

只有在 Token 被綁定到 Client 憑證,而且 Resource Server 也確實比對憑證時,mTLS 才能限制偷來的 Access Token 被冒用。

Token 裡怎麼記錄憑證綁定

如果 Access Token 是 JWT,憑證綁定資訊通常會放在 cnf 欄位中。RFC 8705 使用 x5t#S256 表示 Client 憑證的 SHA-256 指紋:

{
  "cnf": {
    "x5t#S256": "BASE64URL_SHA256_OF_CLIENT_CERTIFICATE"
  }
}

也就是說,Token 裡會記著它應該搭配哪一張 Client 憑證使用。

Resource Server 收到請求後,會從 mTLS 連線取得本次 Client 憑證,計算憑證指紋,再和 Token 裡的 x5t#S256 比對。如果不一致,即使 Token 尚未過期,也應拒絕請求。

因此,mTLS 的重點可以整理為:它是透過 TLS 連線中的 Client 憑證,確認拿 Token 來呼叫 API 的 Client 是否符合原本的綁定。 下一節要看的 DPoP,目的也很接近,只是它不靠 TLS Client 憑證,而是改用每次請求產生的簽章證明。


六、DPoP:用每次請求的簽章證明綁定 Token

前一節的 mTLS 是透過 TLS 連線中的 Client 憑證,確認呼叫 API 的 Client 是否符合 Token 綁定。DPoP(Demonstrating Proof of Possession)則改用另一種方式:Client 每次送出請求時,都要另外附上一份用私鑰簽署的證明。

DPoP Proof JWT 是 Client 用私鑰簽出來的證明。Resource Server 會用它確認:這次拿 Access Token 呼叫 API 的 Client,是否真的持有綁定的私鑰。

https://ithelp.ithome.com.tw/upload/images/20260916/20181928EZdUp8bmw2.png

第一階段:取得綁定的 Access Token

  1. Client 建立公私鑰: Client 先建立一組公私鑰,並保護好私鑰。
  2. Client 產生 DPoP Proof: 向 Authorization Server 請求 Token 時,Client 會用私鑰簽出一份 Proof,並把它放在 DPoP Header。
  3. Authorization Server 綁定 Token: Authorization Server 驗證授權條件與 Proof 後,核發 Access Token,並把這顆 Token 綁定到 Proof 中的公開金鑰。
  4. 回傳 DPoP Token: 如果綁定成功,回應中的 token_type 會是 DPoP。

第二階段:使用 Access Token 呼叫 API

  1. Client 產生新的 Proof: 每次呼叫 API 前,Client 都會針對該次請求產生新的 DPoP Proof。
  2. Client 同時送出 Token 與 Proof: API 請求會包含 Access Token,也會包含這次請求專用的 DPoP Proof。
  3. Resource Server 驗證請求: Resource Server 會檢查 Access Token 是否有效、Proof 簽章是否正確,以及 Proof 使用的金鑰是否符合 Token 的綁定資訊。
  4. 驗證通過後才放行: 只有 Token、Proof 與金鑰綁定都正確,API 才會接受請求。

因此,DPoP 的核心可以整理成一句話:偷到 Access Token 還不夠,攻擊者還必須能用原本綁定的私鑰簽出正確的 Proof。

Access Token 與 DPoP Proof 的差異

使用 DPoP 時,API 請求裡會同時出現兩份資料:

資料 由誰產生 主要用途
Access Token Authorization Server 核發 表示可以存取哪些資源,並記錄它綁定哪一把公開金鑰
DPoP Proof JWT Client 使用私鑰簽署 證明這次請求是由持有對應私鑰的 Client 發出

Access Token 表示授權,DPoP Proof 表示持有證明。兩者要一起檢查,才有辦法限制偷來的 Token 被冒用。

HTTP 請求會長什麼樣子

Client 向 Token Endpoint 換 Token 時,會多帶一個 DPoP Header。下面範例中的 DPoP: PROOF_JWT_FOR_TOKEN_REQUEST 這一行,就是 DPoP Header:

POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: PROOF_JWT_FOR_TOKEN_REQUEST

grant_type=authorization_code&client_id=example-client&code=AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&code_verifier=PKCE_VERIFIER

如果 Authorization Server 接受 DPoP 綁定,回傳的 Token Type 會是 DPoP:

{
  "access_token": "DPOP_BOUND_ACCESS_TOKEN",
  "token_type": "DPoP",
  "expires_in": 600
}

之後呼叫 API 時,Client 會同時送出 Access Token 與新的 Proof:

GET /payroll HTTP/1.1
Host: api.example.com
Authorization: DPoP DPOP_BOUND_ACCESS_TOKEN
DPoP: NEW_PROOF_JWT_FOR_THIS_API_REQUEST

這裡要注意:

  • Authorization: DPoP ... 後面放的是 Access Token。
  • DPoP: ... 後面放的是 這次請求專用的 Proof JWT。

只把 Header 從 Bearer 改成 DPoP 並不代表請求已具備 DPoP 保護。Access Token 要先記錄它綁定哪一把 Client 金鑰;之後 API 收到請求時,也要確認這次送來的 Proof 是用同一把金鑰簽出來的。

Resource Server 主要檢查什麼

DPoP Proof 是一顆簽署過的 JWT,裡面會放這次請求相關的資訊。可以先從以下幾個欄位理解 Proof 的驗證重點:

欄位 用途
jwk Client 的公開金鑰,用來驗證 Proof 簽章
htm 本次 HTTP Method,例如 GET 或 POST
htu 本次請求的目標 URI
iat Proof 建立時間
jti Proof 的唯一編號,可用來偵測重複使用
ath Access Token 的雜湊,讓 Proof 對應到這次送出的 Token

Resource Server 收到請求後,會檢查:

  1. Access Token 是否有效。
  2. DPoP Proof 的簽章是否正確。
  3. Proof 使用的公開金鑰,是否符合 Access Token 裡記錄的綁定資訊。
  4. Proof 裡的 Method、URI 與 Access Token 是否對應本次請求。
  5. 這份 Proof 是否已經被重複使用。

其中最重要的是第三點。任何人都可以自己產生一組金鑰並簽出 Proof;只有確認 Proof 的金鑰和 Access Token 綁定的金鑰一致,才能阻止攻擊者用自己的私鑰搭配偷來的 Token。

DPoP Nonce 與 OIDC Nonce 不同

DPoP 也可能使用 Nonce,但它和前一篇的 OIDC Nonce 不是同一件事。

  • OIDC Nonce: 由 Client 放進認證請求,後續用來確認 ID Token 是否屬於本次登入流程。
  • DPoP Nonce: 由 Authorization Server 或 Resource Server 產生,並要求 Client 放進下一次的 DPoP Proof。伺服器收到 Proof 後,會檢查其中的 Nonce 是否正確,以確認這份 Proof 是依照伺服器最新要求產生的。

OIDC Nonce 主要在對應登入流程;DPoP Nonce 主要在降低登入後呼叫 API 時,舊的 DPoP Proof 被重複使用的風險。


七、mTLS 與 DPoP 的差異

mTLS 與 DPoP 都屬於 Sender-Constrained Token,差別在於 Client 要用什麼方式證明自己持有綁定的私鑰。

兩者的差異整理如下:

比較項目 mTLS/Certificate-Bound Token DPoP
核心規格 RFC 8705 RFC 9449
證明方式 透過 TLS 握手中的 Client 憑證,證明 Client 持有對應私鑰 透過 DPoP Proof JWT,證明 Client 持有對應私鑰
Token 綁定對象 X.509 Client 憑證 DPoP 公開金鑰
常見綁定欄位 cnf.x5t#S256 cnf.jkt
呼叫 API 時怎麼帶 使用 mTLS 連線傳送 Access Token,HTTP Header 可沿用 Bearer 同時帶 Authorization: DPoP 與 DPoP Header
是否需要 Client 憑證 需要 不需要,使用公私鑰與 JWK
較適合的情境 後端服務、企業整合,或已經有憑證管理與 mTLS 基礎設施的系統 SPA、行動 App,或較適合在應用層處理簽章的系統
主要成本 憑證生命週期、TLS 終止位置、憑證資訊傳遞 金鑰保存、每次請求簽署與驗證、Nonce 與 Replay Cache

mTLS 適合已有憑證管理的系統;DPoP 適合用應用層簽章處理的 Client。

另外,DPoP 仍然需要 HTTPS。它只負責證明 Client 持有私鑰,不能取代 TLS。


八、實際部署時要注意的限制

mTLS 與 DPoP 能降低 Access Token 外洩後被直接冒用的風險,但並不代表 Token 安全問題已完全解決。實際部署時,仍需留意以下限制。

  • 仍然要避免 Token 外洩: Token Endpoint 與 API 仍需使用 HTTPS,也應避免把 Access Token、Authorization Header 或 Refresh Token 寫入 Log、Trace 與錯誤報告。
  • Client 被入侵時,保護效果會下降: 如果攻擊者已經能在 Client 環境中執行程式,就可能濫用簽章功能或操作私鑰。就算私鑰不能被複製,惡意程式仍可能直接在 Client 裡使用這把私鑰產生簽章。
  • Resource Server 必須實際驗證綁定: API 端要檢查 Token 是否有效,也要檢查 mTLS 憑證或 DPoP Proof 是否符合 Token 的綁定資訊。若驗證失敗,不能退回一般 Bearer Token 接受請求。

Sender-Constrained Token 的重點不是取代既有防護,而是在 Token 已經外洩時,多加一道限制。


小結

Access Token 外洩後,系統不能只判斷 Token 是否有效,還要確認目前使用 Token 的 Client 是否符合原本的綁定。

因此,本篇可以整理成三個重點:

  • Bearer Token 是持有即可使用: 簽章能保證內容沒被改,但不能證明持有人就是原本的 Client。
  • 短效期限、Scope 與 Audience 只能降低影響範圍: 它們可以限制 Token 的使用時間、操作權限與適用 API,但無法單獨阻止有效範圍內的冒用。
  • mTLS 與 DPoP 進一步要求持有證明: Resource Server 不只檢查 Token,也要確認呼叫 API 的 Client 是否持有與 Token 綁定的憑證或私鑰。

Token 外洩後仍保留第二道限制,即使攻擊者取得 Access Token,也必須同時具備對應的憑證或私鑰,才有可能完成 API 存取。

下一篇會接著討論重放攻擊、時序攻擊與競態條件,說明驗證流程中為什麼不能只看「資料是否有效」,也要確認請求是否符合當下的時間、狀態與一次性使用條件。


參考資源


上一篇
Day 27|被劫截的授權碼:中間人如何攻擊 OAuth/OIDC
下一篇
Day 29|重放攻擊、時序攻擊與競態條件:驗證流程中的時間與狀態陷阱
系列文
從登入到授權-現代軟體的身分架構指南 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言